<!DOCTYPE html>
<html>

<head>
  <title>Test document root content mutations</title>
  <link rel="stylesheet" type="text/css"
        href="chrome://mochikit/content/tests/SimpleTest/test.css" />

  <script type="application/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>

  <script type="application/javascript"
          src="../common.js"></script>
  <script type="application/javascript"
          src="../role.js"></script>
  <script type="application/javascript"
          src="../states.js"></script>
  <script type="application/javascript"
          src="../events.js"></script>

  <script type="application/javascript">

    ////////////////////////////////////////////////////////////////////////////
    // Helpers

    function getDocNode(aID)
    {
      return getNode(aID).contentDocument;
    }
    function getDocChildNode(aID)
    {
      return getDocNode(aID).body.firstChild;
    }

    function rootContentReplaced(aID, aTextName, aRootContentRole)
    {
      this.eventSeq = [
        new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
        new invokerChecker(EVENT_REORDER, getDocNode, aID)
      ];

      this.finalCheck = function rootContentReplaced_finalCheck()
      {
        var tree = {
          role: aRootContentRole || ROLE_DOCUMENT,
          children: [
            {
              role: ROLE_TEXT_LEAF,
              name: aTextName
            }
          ]
        };
        testAccessibleTree(getDocNode(aID), tree);
      }
    }

    function rootContentRemoved(aID)
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, null),
        new invokerChecker(EVENT_REORDER, getDocNode, aID)
      ];

      this.preinvoke = function rootContentRemoved_preinvoke()
      {
        // Set up target for hide event before we invoke.
        var text = getAccessible(getAccessible(getDocNode(aID)).firstChild);
        this.eventSeq[0].target = text;
      }

      this.finalCheck = function rootContentRemoved_finalCheck()
      {
        var tree = {
          role: ROLE_DOCUMENT,
          children: [ ]
        };
        testAccessibleTree(getDocNode(aID), tree);
      }
    }

    function rootContentInserted(aID, aTextName)
    {
      this.eventSeq = [
        new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
        new invokerChecker(EVENT_REORDER, getDocNode, aID)
      ];

      this.finalCheck = function rootContentInserted_finalCheck()
      {
        var tree = {
          role: ROLE_DOCUMENT,
          children: [
            {
              role: ROLE_TEXT_LEAF,
              name: aTextName
            }
          ]
        };
        testAccessibleTree(getDocNode(aID), tree);
      }
    }

    ////////////////////////////////////////////////////////////////////////////
    // Invokers

    function writeIFrameDoc(aID)
    {
      this.__proto__ = new rootContentReplaced(aID, "hello");

      this.invoke = function writeIFrameDoc_invoke()
      {
        var docNode = getDocNode(aID);

        // We can't use open/write/close outside of iframe document because of
        // security error.
        var script = docNode.createElement("script");
        script.textContent = "document.open(); document.write('hello'); document.close();";
        docNode.body.appendChild(script);
      }

      this.getID = function writeIFrameDoc_getID()
      {
        return "write document";
      }
    }

    /**
     * Replace HTML element.
     */
    function replaceIFrameHTMLElm(aID)
    {
      this.eventSeq = [
        new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
        new invokerChecker(EVENT_REORDER, getDocNode, aID)
      ];

      this.invoke = function replaceIFrameHTMLElm_invoke()
      {
        var docNode = getDocNode(aID);
        var newHTMLNode = docNode.createElement("html");
        newHTMLNode.innerHTML = `<body><p>New Wave</p></body`;
        docNode.replaceChild(newHTMLNode, docNode.documentElement);
      }

      this.finalCheck = function replaceIFrameHTMLElm_finalCheck()
      {
        var tree = {
          role: ROLE_DOCUMENT,
          children: [
            {
              role: ROLE_PARAGRAPH,
              children: [
                {
                  role: ROLE_TEXT_LEAF,
                  name: 'New Wave'
                }
              ]
            }
          ]
        };
        testAccessibleTree(getDocNode(aID), tree);
      }

      this.getID = function replaceIFrameHTMLElm_getID()
      {
        return "replace HTML element";
      }
    }

    /**
     * Replace HTML body on new body having ARIA role.
     */
    function replaceIFrameBody(aID)
    {
      this.__proto__ = new rootContentReplaced(aID, "New Hello");

      this.invoke = function replaceIFrameBody_invoke()
      {
        var docNode = getDocNode(aID);
        var newBodyNode = docNode.createElement("body");
        var newTextNode = docNode.createTextNode("New Hello");
        newBodyNode.appendChild(newTextNode);
        docNode.documentElement.replaceChild(newBodyNode, docNode.body);
      }

      this.getID = function replaceIFrameBody_getID()
      {
        return "replace body";
      }
    }

    /**
     * Replace HTML body on new body having ARIA role.
     */
    function replaceIFrameBodyOnARIARoleBody(aID)
    {
      this.__proto__ = new rootContentReplaced(aID, "New Hello",
                                               ROLE_PUSHBUTTON);

      this.invoke = function replaceIFrameBodyOnARIARoleBody_invoke()
      {
        var docNode = getDocNode(aID);
        var newBodyNode = docNode.createElement("body");
        var newTextNode = docNode.createTextNode("New Hello");
        newBodyNode.appendChild(newTextNode);
        newBodyNode.setAttribute("role", "button");
        docNode.documentElement.replaceChild(newBodyNode, docNode.body);
      }

      this.getID = function replaceIFrameBodyOnARIARoleBody_getID()
      {
        return "replace body on body having ARIA role";
      }
    }

    /**
     * Open/close document pair.
     */
    function openIFrameDoc(aID)
    {
      this.__proto__ = new rootContentRemoved(aID);

      this.invoke = function openIFrameDoc_invoke()
      {
        this.preinvoke();

        // Open document.
        var docNode = getDocNode(aID);
        var script = docNode.createElement("script");
        script.textContent = "function closeMe() { document.write('Works?'); document.close(); } window.closeMe = closeMe; document.open();";
        docNode.body.appendChild(script);
      }

      this.getID = function openIFrameDoc_getID()
      {
        return "open document";
      }
    }

    function closeIFrameDoc(aID)
    {
      this.__proto__ = new rootContentInserted(aID, "Works?");

      this.invoke = function closeIFrameDoc_invoke()
      {
        // Write and close document.
        getDocNode(aID).write('Works?'); getDocNode(aID).close();
      }

      this.getID = function closeIFrameDoc_getID()
      {
        return "close document";
      }
    }

    /**
     * Remove/insert HTML element pair.
     */
    function removeHTMLFromIFrameDoc(aID)
    {
      this.__proto__ = new rootContentRemoved(aID);

      this.invoke = function removeHTMLFromIFrameDoc_invoke()
      {
        this.preinvoke();

        // Remove HTML element.
        var docNode = getDocNode(aID);
        docNode.removeChild(docNode.firstChild);
      }

      this.getID = function removeHTMLFromIFrameDoc_getID()
      {
        return "remove HTML element";
      }
    }

    function insertHTMLToIFrameDoc(aID)
    {
      this.__proto__ = new rootContentInserted(aID, "Haha");

      this.invoke = function insertHTMLToIFrameDoc_invoke()
      {
        // Insert HTML element.
        var docNode = getDocNode(aID);
        var html = docNode.createElement("html");
        var body = docNode.createElement("body");
        var text = docNode.createTextNode("Haha");
        body.appendChild(text);
        html.appendChild(body);
        docNode.appendChild(html);
      }

      this.getID = function insertHTMLToIFrameDoc_getID()
      {
        return "insert HTML element document";
      }
    }

    /**
     * Remove/insert HTML body pair.
     */
    function removeBodyFromIFrameDoc(aID)
    {
      this.__proto__ = new rootContentRemoved(aID);

      this.invoke = function removeBodyFromIFrameDoc_invoke()
      {
        this.preinvoke();

        // Remove body element.
        var docNode = getDocNode(aID);
        docNode.documentElement.removeChild(docNode.body);
      }

      this.getID = function removeBodyFromIFrameDoc_getID()
      {
        return "remove body element";
      }
    }

    function insertElmUnderDocElmWhileBodyMissed(aID)
    {
      this.docNode = null;
      this.inputNode = null;

      function getInputNode()
        { return this.inputNode; }

      this.eventSeq = [
        new invokerChecker(EVENT_SHOW, getInputNode.bind(this)),
        new invokerChecker(EVENT_REORDER, getDocNode, aID)
      ];

      this.invoke = function invoke()
      {
        this.docNode = getDocNode(aID);
        this.inputNode = this.docNode.createElement("input");
        this.docNode.documentElement.appendChild(this.inputNode);
      }

      this.finalCheck = function finalCheck()
      {
        var tree =
          { DOCUMENT: [
            { ENTRY: [ ] }
          ] };
        testAccessibleTree(this.docNode, tree);

        // Remove aftermath of this test before next test starts.
        this.docNode.documentElement.removeChild(this.inputNode);
      }

      this.getID = function getID()
      {
        return "Insert element under document element while body is missed.";
      }
    }

    function insertBodyToIFrameDoc(aID)
    {
      this.__proto__ = new rootContentInserted(aID, "Yo ho ho i butylka roma!");

      this.invoke = function insertBodyToIFrameDoc_invoke()
      {
        // Insert body element.
        var docNode = getDocNode(aID);
        var body = docNode.createElement("body");
        var text = docNode.createTextNode("Yo ho ho i butylka roma!");
        body.appendChild(text);
        docNode.documentElement.appendChild(body);
      }

      this.getID = function insertBodyToIFrameDoc_getID()
      {
        return "insert body element";
      }
    }

    function changeSrc(aID)
    {
      this.containerNode = getNode(aID);

      this.eventSeq = [
        new invokerChecker(EVENT_REORDER, this.containerNode)
      ];

      this.invoke = function changeSrc_invoke()
      {
        this.containerNode.src = "data:text/html,<html><input></html>";
      }

      this.finalCheck = function changeSrc_finalCheck()
      {
        var tree =
          { INTERNAL_FRAME: [
            { DOCUMENT: [
              { ENTRY: [ ] }
            ] }
          ] };
        testAccessibleTree(this.containerNode, tree);
      }

      this.getID = function changeSrc_getID()
      {
        return "change src on iframe";
      }
    }

    ////////////////////////////////////////////////////////////////////////////
    // Test

    //gA11yEventDumpToConsole = true;
    //enableLogging('tree,verbose');

    var gQueue = null;

    function doTest()
    {
      gQueue = new eventQueue();

      gQueue.push(new writeIFrameDoc("iframe"));
      gQueue.push(new replaceIFrameHTMLElm("iframe"));
      gQueue.push(new replaceIFrameBody("iframe"));
      gQueue.push(new openIFrameDoc("iframe"));
      gQueue.push(new closeIFrameDoc("iframe"));
      gQueue.push(new removeHTMLFromIFrameDoc("iframe"));
      gQueue.push(new insertHTMLToIFrameDoc("iframe"));
      gQueue.push(new removeBodyFromIFrameDoc("iframe"));
      gQueue.push(new insertElmUnderDocElmWhileBodyMissed("iframe"));
      gQueue.push(new insertBodyToIFrameDoc("iframe"));
      gQueue.push(new changeSrc("iframe"));
      gQueue.push(new replaceIFrameBodyOnARIARoleBody("iframe"));

      gQueue.invoke(); // SimpleTest.finish() will be called in the end
    }

    SimpleTest.waitForExplicitFinish();
    addA11yLoadEvent(doTest);
  </script>
</head>
<body>

  <a target="_blank"
     title="Update accessible tree when root element is changed"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=606082">Mozilla Bug 606082</a>
  <a target="_blank"
     title="Elements inserted outside the body aren't accessible"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a>
  <a target="_blank"
     title="Reorder event for document must be fired after document initial tree creation"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=669263">Mozilla Bug 669263</a>
  <a target="_blank"
     title="Changing the HTML body doesn't pick up ARIA role"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=818407">Mozilla Bug 818407</a>

  <p id="display"></p>
  <div id="content" style="display: none"></div>
  <pre id="test">
  </pre>

  <iframe id="iframe"></iframe>

  <div id="eventdump"></div>
</body>
</html>
